package com.dragon.blelibrary.scandevise;

import android.bluetooth.BluetoothDevice;
import android.util.Log;

import com.dragon.blelibrary.bleutils.BleLOG;

import java.io.UnsupportedEncodingException;
import java.util.UUID;

public class ScannerServiceParser
{
	private static final String TAG = "ScannerServiceParser".toString();
	
	private static final int SERVICES_MORE_AVAILABLE_16_BIT = 0x02;
	private static final int SERVICES_COMPLETE_LIST_16_BIT = 0x03;
	private static final int SERVICES_MORE_AVAILABLE_32_BIT = 0x04;
	private static final int SERVICES_COMPLETE_LIST_32_BIT = 0x05;
	private static final int SERVICES_MORE_AVAILABLE_128_BIT = 0x06;
	private static final int SERVICES_COMPLETE_LIST_128_BIT = 0x07;
	private static final int SHORTENED_LOCAL_NAME = 0x08;
	private static final int COMPLETE_LOCAL_NAME = 0x09;
	
	private int packetLength = 0;
	private boolean isValidSensor = false;
	private String requiredUUID;
	private static ScannerServiceParser instance;
	
	public static synchronized ScannerServiceParser getInstance()
	{
		if(instance == null)
		{
			instance = new ScannerServiceParser();
		}
		return instance;
	}
	
	public boolean isValidSensor()
	{
		return isValidSensor;
	}
	
	public void decodeDeviceAdvData(byte[] data, UUID requiredUUID) throws Exception
	{
		isValidSensor = false;
		this.requiredUUID = requiredUUID.toString();
		if(data != null)
		{
			int fieldLength, fieldName;
			packetLength = data.length;
			for(int index=0; index < packetLength; index++)
			{
				fieldLength = data[index];
				if(fieldLength == 0)
				{
					BleLOG.i(TAG, "index: " + index + ", No more data exist in advertisement packet.");
					return;
				}
				fieldName = data[++index];
				BleLOG.i(TAG, "FiledLength: " + fieldLength + ", FieldName: " + fieldName);
				
				if(fieldName == SERVICES_MORE_AVAILABLE_16_BIT || fieldName == SERVICES_COMPLETE_LIST_16_BIT)
				{
					BleLOG.i(TAG, "index: " + index + ", Service class 16 bit UUID exist.");
					for(int i=index+1; i < index + fieldLength - 1; i+=2)
					{
						decodeService16BitUUID(data, i, 2);
					}
				}
				else if(fieldName == SERVICES_MORE_AVAILABLE_32_BIT || fieldName == SERVICES_COMPLETE_LIST_32_BIT)
				{
					BleLOG.i(TAG, "index: " + index + ", Service class 32 bit UUID exist.");
					for(int i=index+1; i < index + fieldLength - 1; i+=4)
					{
						decodeService32BitUUID(data, i, 4);
					}
				}
				else if(fieldName == SERVICES_MORE_AVAILABLE_128_BIT || fieldName == SERVICES_COMPLETE_LIST_128_BIT)
				{
					BleLOG.i(TAG, "index: " + index + ", Service class 128 bit UUID exist.");
					for(int i=index+1; i < index + fieldLength - 1; i+=16)
					{
						decodeService128BitUUID(data, i, 16);
					}
				}
				index += fieldLength - 1;
			}
		}
		else
		{
			BleLOG.w(TAG, "data is null.");
		}
	}
	
	private void decodeService16BitUUID(byte[] data, int startPosition, int serviceDataLength) throws Exception
	{
		BleLOG.i(TAG, "startPosition: " + startPosition + ", serviceDataLength: " + serviceDataLength);
		String serviceUUID = Integer.toHexString(decodeUUID16(data, startPosition));
		String requireUUID = this.requiredUUID.substring(4, 8);
		BleLOG.i(TAG, "serviceUUID: " + serviceUUID + ", requireUUID: " + requireUUID);
		
		if(serviceUUID.equals(requireUUID))
		{
			BleLOG.i(TAG, "requiredUUID exists.");
			isValidSensor = true;
		}
	}
	
	private void decodeService32BitUUID(byte[] data, int startPosition, int serviceDataLength) throws Exception
	{
		BleLOG.i(TAG, "startPosition: " + startPosition + ", serviceDataLength: " + serviceDataLength);
		String serviceUUID = Integer.toHexString(decodeUUID16(data, startPosition + serviceDataLength - 4));
		String requireUUID = this.requiredUUID.substring(4, 8);
		BleLOG.i(TAG, "serviceUUID: " + serviceUUID + ", requireUUID: " + requireUUID);
		
		if(serviceUUID.equals(requireUUID))
		{
			BleLOG.i(TAG, "requiredUUID exists.");
			isValidSensor = true;
		}
	}
	
	private void decodeService128BitUUID(byte[] data, int startPosition, int serviceDataLength) throws Exception
	{
		BleLOG.i(TAG, "startPosition: " + startPosition + ", serviceDataLength: " + serviceDataLength);
		String serviceUUID = Integer.toHexString(decodeUUID16(data, startPosition + serviceDataLength -4));
		String requireUUID = this.requiredUUID.substring(4, 8);
		BleLOG.i(TAG, "serviceUUID: " + serviceUUID + ", requireUUID: " + requireUUID);
		
		if(serviceUUID.equals(requireUUID))
		{
			BleLOG.i(TAG, "requiredUUID exists.");
			isValidSensor = true;
		}
	}
	
	private int decodeUUID16(final byte[] data, final int start)
	{
		
		final int b1 = data[start] & 0xff;
		final int b2 = data[start+1] & 0xff;
		return (b2 << 8 | b1 << 0);
	}
	
	/**
	 * Decodes the device name from Complete Local Name or Shortened Local Name field in Advertisement packet. Ususally if should be done by {@link BluetoothDevice#getName()} method but some phones
	 * skips that, f.e. Sony Xperia Z1 (C6903) with Android 4.3 where getName() always returns <code>null</code>. In order to show the device name correctly we have to parse it manually :(
	 */
	public static String decodeDeviceName(byte[] data) {
		String name = null;
		int fieldLength, fieldName;
		int packetLength = data.length;
		for (int index = 0; index < packetLength; index++) {
			fieldLength = data[index];
			if (fieldLength == 0)
				break;
			fieldName = data[++index];

			if (fieldName == COMPLETE_LOCAL_NAME || fieldName == SHORTENED_LOCAL_NAME) {
				name = decodeLocalName(data, index + 1, fieldLength - 1);
				break;
			}
			index += fieldLength - 1;
		}
		return name;
	}

	/**
	 * Decodes the local name
	 */
	public static String decodeLocalName(final byte[] data, final int start, final int length) {
		try {
			return new String(data, start, length, "UTF-8");
		} catch (final UnsupportedEncodingException e) {
			Log.e(TAG, "Unable to convert the complete local name to UTF-8", e);
			return null;
		} catch (final IndexOutOfBoundsException e) {
			Log.e(TAG, "Error when reading complete local name", e);
			return null;
		}
	}

}
